package com.ice.proxy.controller;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.springframework.web.bind.annotation.RequestParam;
import com.ice.proxy.handler.IExecutorHandler;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by ice on 7/23/16.
 */
@RestController
@RequestMapping("/proxy")
public class ProxyController {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private IExecutorHandler executor;

    /**
     * 代理内部所有开放业务方法调用
     *
     * @param request 请求对象
     * @param className 类名称
     * @param methodName 方法名称
     * @param params 参数结构体
     * @return 方法返回结果
     */
    @RequestMapping("/{class}/{method}")
    @ResponseBody
    public Object proxy(HttpServletRequest request, @PathVariable("class") String className, @PathVariable("method") String methodName, @RequestParam("params") String params) {
        Object result = null;
        try {
            Class<?> clazz = Class.forName(className);
            WebApplicationContext context = (WebApplicationContext) request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> map = mapper.readValue(params, TreeMap.class);
            List<Object> paramList = new ArrayList<>();
            for (String variable : map.keySet()) {
                String[] param = variable.split(":");
                paramList.add(Class.forName(param[0]));
            }
            Method method = clazz.getMethod(methodName, paramList.toArray(new Class[]{}));
            Object bean = context.getBean(clazz);

            result = executor.invoke(method, bean, map.values().toArray());
        } catch (ClassNotFoundException e) {
            logger.error("没有相应的类型", e);
        } catch (NoSuchMethodException e) {
            logger.error("没有相应的方法", e);
        } catch (JsonParseException e) {
            logger.error("json解析异常", e);
        } catch (JsonMappingException e) {
            logger.error("json映射异常", e);
        } catch (IOException e) {
            logger.error("json序列化出现异常", e);
        }
        return result;
    }

    /**
     * 获取服务信息明细
     *
     * @param request 请求对象
     * @param service serviceId(属性文件service.properties中配置的服务id)
     * @return 服务信息明细
     */
    @RequestMapping("/getServiceInfo")
    @ResponseBody
    public Object getServiceInfo(HttpServletRequest request, @RequestParam(name = "type", required = true, defaultValue = "all") String service) {
        Object result = null;
        try {
            if (service.equals("all")) {
                Properties properties = PropertiesLoaderUtils.loadProperties(new ClassPathResource("service.properties"));
                result = properties.get("services");
                return result;
            }
            Map<String, Map<String, Object>> map = new HashMap<>();
            WebApplicationContext context = (WebApplicationContext) request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            Object serviceObj = context.getBean(service);
            ClassPool pool = ClassPool.getDefault();
            CtClass clazz = pool.get(serviceObj.getClass().getName());
            CtMethod[] methods = clazz.getDeclaredMethods();
            for (CtMethod method : methods) {
                MethodInfo methodInfo = method.getMethodInfo2();
                CodeAttribute attribute = methodInfo.getCodeAttribute();
                LocalVariableAttribute variable = (LocalVariableAttribute) attribute.getAttribute(LocalVariableAttribute.tag);
                Map<String, Object> params = new HashMap<>();
                if (variable == null) {
                    params.put(methodInfo.getName(), null);
                    continue;
                } else {
                    String[] paramNames = new String[method.getParameterTypes().length];
                    int position = Modifier.isStatic(method.getModifiers()) ? 0 : 1;
                    StringBuilder param = new StringBuilder();
                    for (int i = 0; i < paramNames.length; i++) {
                        param.append(method.getParameterTypes()[i].getName());
                        param.append(":");
                        param.append(variable.variableName(i + position));
                    }
                    StringBuilder methodName = new StringBuilder();
                    methodName.append(method.getReturnType().getName());
                    methodName.append(":");
                    methodName.append(methodInfo.getName());
                    params.put(methodName.toString(), param.toString());
                }
                map.put(clazz.getName(), params);
            }
            return map;
        } catch (NotFoundException ex) {
            logger.error("没有找到对应的类对象", ex);
            return result;
        } catch (IOException ex) {
            logger.error("读取属性文件失败", ex);
            return result;
        }
    }
}
